package xworker.javafx.beans.property;

import javafx.beans.property.*;
import javafx.scene.control.TextInputControl;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
 * 由于使用Graalvm打包成Native Image时Ognl等脚本语言因反射的原因失败，因此在此人工注册方法。
 */
public class PropertyFactory {
    private static final Map<Class<?>, Map<String, Function<Object, Property<?>>>> propertyGetters = new HashMap<>();
    static{
        regist(TextInputControl.class, "editableProperty", o -> {
            TextInputControl obj = (TextInputControl) o;
            return obj.editableProperty();
        });
    }

    public static void regist(Class<?> cls, String name, Function<Object, Property<?>> getter){
        Map<String, Function<Object, Property<?>>> getters = propertyGetters.computeIfAbsent(cls, k -> new HashMap<>());

        getters.put(name, getter);
    }

    public static Property<?> getProperty(Object obj, String name){
        if(name.endsWith("()")){
            name = name.substring(0, name.length() - 2);
        }
        Class<?> cls = obj.getClass();
        Map<String, Function<Object, Property<?>>> geterrs = propertyGetters.get(cls);
        if(geterrs != null){
            Function<Object, Property<?>> getter = geterrs.get(name);
            if(getter != null){
                return getter.apply(obj);
            }
        }

        Class<?> superCls = cls.getSuperclass();
        while(superCls != null){
            geterrs = propertyGetters.get(superCls);
            if(geterrs != null){
                Function<Object, Property<?>> getter = geterrs.get(name);
                if(getter != null){
                    return getter.apply(obj);
                }
            }

            superCls = superCls.getSuperclass();
        }

        return null;
    }

    private static boolean isProperty(Class<?> cls){
        for(Class<?> c : cls.getInterfaces()){
            if(c == Property.class){
                return true;
            }
        }

        return false;
    }

    public static String generateRegistJavaCode(Class<?> cls){
        StringBuilder sb = new StringBuilder("    static{\n");
        for(Method method : cls.getDeclaredMethods()){
            if((method.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC && method.getParameterTypes().length == 0){
                Class<?> returnType = method.getReturnType();
                if(isProperty(returnType)) {

                    String name = method.getName();
                    sb.append("        PropertyFactory.regist(").append(cls.getSimpleName()).append(".class, \"").append(name).append("\", o -> {\n");
                    sb.append("            ").append(cls.getSimpleName()).append(" obj = (").append(cls.getSimpleName()).append(") o;\n");
                    sb.append("            return obj.").append(name).append("();\n");
                    sb.append("        });\n");
                }
            }
        }
        sb.append("    }");

        return sb.toString();
    }

    private static String getPropertyTypeThingPath(Class<?> cls){
        Class<?> superCls = cls;
        while(superCls != null){
            if(superCls == BooleanProperty.class){
                return "xworker.javafx.beans.property.BooleanProperty";
            }else if(superCls == DoubleProperty.class){
                return "xworker.javafx.beans.property.DoubleProperty";
            }else if(superCls == FloatProperty.class){
                return "xworker.javafx.beans.property.FloatProperty";
            }else if(superCls == IntegerProperty.class){
                return "xworker.javafx.beans.property.IntegerProperty";
            }else if(superCls == ListProperty.class){
                return "xworker.javafx.beans.property.ListProperty";
            }else if(superCls == LongProperty.class){
                return "xworker.javafx.beans.property.LongProperty";
            }else if(superCls == MapProperty.class){
                return "xworker.javafx.beans.property.MapProperty";
            }else if(superCls == SetProperty.class){
                return "xworker.javafx.beans.property.SetProperty";
            }else if(superCls == StringProperty.class){
                return "xworker.javafx.beans.property.StringProperty";
            }

            superCls = superCls.getSuperclass();
        }
        for(Class<?> c : cls.getInterfaces()){
            if(c == BooleanProperty.class){
                return "xworker.javafx.beans.property.BooleanProperty";
            }else if(c == DoubleProperty.class){
                return "xworker.javafx.beans.property.DoubleProperty";
            }else if(c == FloatProperty.class){
                return "xworker.javafx.beans.property.FloatProperty";
            }else if(c == IntegerProperty.class){
                return "xworker.javafx.beans.property.IntegerProperty";
            }else if(c == ListProperty.class){
                return "xworker.javafx.beans.property.ListProperty";
            }else if(c == LongProperty.class){
                return "xworker.javafx.beans.property.LongProperty";
            }else if(c == MapProperty.class){
                return "xworker.javafx.beans.property.MapProperty";
            }else if(c == SetProperty.class){
                return "xworker.javafx.beans.property.SetProperty";
            }else if(c == StringProperty.class){
                return "xworker.javafx.beans.property.StringProperty";
            }
        }

        return "xworker.javafx.beans.property.ObjectProperty";
    }

    public static String generateProperiesChildThingCode(Class<?> cls){
        StringBuilder sb = new StringBuilder("<?xml version='1.0' encoding='utf-8'?>\n" +
                "\n<thing>\n");
        for(Method method : cls.getDeclaredMethods()){
            if((method.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC && method.getParameterTypes().length == 0){
                Class<?> returnType = method.getReturnType();
                if(isProperty(returnType)) {
                    String name = method.getName();
                    String extendPath = getPropertyTypeThingPath(returnType);
                    sb.append("    <thing name=\"").append(name).append("\" group=\"properties\" descriptors=\"xworker.lang.MetaDescriptor3\" " +
                            "extends=\"xworker.javafx.beans.property.ParentProperty,").append(extendPath).append("\"/>\n");
                }
            }
        }
        sb.append("</thing>");
        return sb.toString();
    }
}
